package sample.magic_square_sol;

import javafx.scene.control.Label;
import sample.MagicSquareController;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class SimulatedAnnealingSolver implements MagicSquareSolver {
    static int[][] fixSquare;
    static int[] sumRow;
    static int[] sumColumn;
    static int leftDiagonal;
    static int rightDiagonal;

    static int[][] mutatedSquare;

    private double INTIAL_TEMPERATURE = 1;
    private int STEPS_PER_ITERATION = 100;
    private double COOLING_FRACTION = 0.97;

    private Random rand;
    private ProgOptions options;

    public SimulatedAnnealingSolver(ProgOptions progOptions) {
        rand = new Random();
        options = progOptions;
    }



    public MagicSquareSolution solve(long allowedTimeNanoSec, int[][] data, boolean[][] isFixed) {
        sumRow = new int[options.n];
        sumColumn = new int[options.n];
        int[][] primarySquare = new int[options.n][options.n];
        if (options.constrained == true) {
            for(int i=0;i<options.n;i++){
                for (int j = 0; j < options.n; j++){
                    if(isFixed[i+1][j+1]){
                        primarySquare[i][j] = data[i+1][j+1];
                    }
                    else {
                        primarySquare[i][j] = 0;
                    }
                }
            }

        }

        int[][] square = makeInitialSquare(rand, options.n,
                options.constrained, options.cRow, options.cCol, primarySquare);


        int fitness = evalFitness(square, options.n, options.m);
        MagicSquareController.error = fitness;

        int iteration = 0;
        MagicSquareController.generation = iteration;

        double temperature = INTIAL_TEMPERATURE;
        int stuckcount = 0;
        long startTime = System.nanoTime();

        do {
            if (fitness == 0) {
                for(int i=0;i<MagicSquareController.dimension;i++){
                    for(int k=0;k<MagicSquareController.dimension;k++){
                        MagicSquareController.data[i+1][k+1] = square[i][k];
                        MagicSquareController.labels[i+1][k+1].setText(""+MagicSquareController.data[i+1][k+1]);
                    }
                }
                MagicSquareController.generationLabel.setText("  Generation: " +MagicSquareController.generation);
                MagicSquareController.errorLabel.setText("  Error: " + MagicSquareController.error);

                //left upper 0
                int s0 = 0;
                for(int i=1;i<=MagicSquareController.dimension;i++){
                    s0 += data[i][i];
                }
                MagicSquareController.sumData[0] = s0;
                MagicSquareController.sumData[1] = s0;
                MagicSquareController.sumLables[0].setText(""+MagicSquareController.sumData[0]);
                System.out.println("更新");
                MagicSquareController.sumLables[1].setText(""+MagicSquareController.sumData[1]);

                //upper 2,4,...,2*dimension
                for(int i=2;i<=2*MagicSquareController.dimension;i+=2){
                    int s = 0;
                    for(int k=1;k<=MagicSquareController.dimension;k++){
                        s += data[i/2][k];
                    }
                    MagicSquareController.sumData[i] = s;
                    MagicSquareController.sumData[i+1] = s;
                    MagicSquareController.sumLables[i].setText(""+MagicSquareController.sumData[i]);
                    MagicSquareController.sumLables[i+1].setText(""+MagicSquareController.sumData[i+1]);
                }

                // right upper 2*dimension+2
                s0 = 0;
                for(int i=1;i<=MagicSquareController.dimension;i++){
                    s0 += data[i][MagicSquareController.dimension+1-i];
                }
                MagicSquareController.sumData[2*MagicSquareController.dimension+2] = s0;
                MagicSquareController.sumData[2*MagicSquareController.dimension+3] = s0;
                MagicSquareController.sumLables[2*MagicSquareController.dimension+2].setText(""+MagicSquareController.sumData[2*MagicSquareController.dimension+2]);
                MagicSquareController.sumLables[2*MagicSquareController.dimension+3].setText(""+MagicSquareController.sumData[2*MagicSquareController.dimension+3]);

                //right 2*dimension+2+i
                for(int i=2; i<=2*MagicSquareController.dimension; i+=2) {
                    int s = 0;
                    for (int k = 1; k <= MagicSquareController.dimension; k++) {
                        s += data[i / 2][k];
                    }
                    MagicSquareController.sumData[2 * MagicSquareController.dimension + 2 + i] = s;
                    MagicSquareController.sumData[2 * MagicSquareController.dimension + 3 + i] = s;
                    MagicSquareController.sumLables[2 * MagicSquareController.dimension + 2 + i].setText("" + MagicSquareController.sumData[2 * MagicSquareController.dimension + 2 + i]);
                    MagicSquareController.sumLables[2 * MagicSquareController.dimension + 3 + i].setText("" + MagicSquareController.sumData[2 * MagicSquareController.dimension + 3 + i]);

                }
                break;
            }

            temperature *= COOLING_FRACTION;
            int tempFitness = fitness;

            boolean flag = false;
            for (int j = 0; j < STEPS_PER_ITERATION; j++) {

                int newFitness = sum(rand, square, options.n,
                        1, options.m, fitness);

                if (newFitness >= fitness) {
                    flag = true;
                } else {
                    flag = false;
                }


                if (newFitness < fitness ||
                        Math.exp((fitness - newFitness) / (temperature)) > rand.nextDouble()) {

                    square = mutatedSquare;


                    fitness = newFitness;
                  if(newFitness>=fitness){


                      int leftToRightDiagSum = 0;
                      int rightToLeftDiagSum = 0;

                      for(int i=0; i<options.n; i++)
                      {
                          int rowSum = 0;
                          int colSum = 0;

                          for(int k=0; k<options.n; k++)
                          {
                              rowSum += square[i][k];
                              colSum += square[k][i];

                              if(i == k)
                              {
                                  leftToRightDiagSum += square[i][k];
                              }

                              if(i+k == options.n-1)
                              {
                                  rightToLeftDiagSum += square[i][k];
                              }
                          }
                          sumRow[i] = rowSum;
                          sumColumn[i]=colSum;

                      }

                      leftDiagonal=leftToRightDiagSum;
                      rightDiagonal=rightToLeftDiagSum;
                  }

                }



                if (fitness == 0) {
                    for(int i=0;i<MagicSquareController.dimension;i++){
                        for(int k=0;k<MagicSquareController.dimension;k++){
                            MagicSquareController.data[i+1][k+1] = square[i][k];
                            MagicSquareController.labels[i+1][k+1].setText(""+MagicSquareController.data[i+1][k+1]);
                        }
                    }
                    MagicSquareController.generationLabel.setText("  Generation: " +MagicSquareController.generation);
                    MagicSquareController.errorLabel.setText("  Error: " +MagicSquareController.error);

                    //left upper 0
                    int s0 = 0;
                    for(int i=1;i<=MagicSquareController.dimension;i++){
                        s0 += data[i][i];
                    }
                    MagicSquareController.sumData[0] = s0;
                    MagicSquareController.sumData[1] = s0;
                    MagicSquareController.sumLables[0].setText(""+MagicSquareController.sumData[0]);
                    System.out.println("更新");
                    MagicSquareController.sumLables[1].setText(""+MagicSquareController.sumData[1]);

                    //upper 2,4,...,2*dimension
                    for(int i=2;i<=2*MagicSquareController.dimension;i+=2){
                        int s = 0;
                        for(int k=1;k<=MagicSquareController.dimension;k++){
                            s += data[i/2][k];
                        }
                        MagicSquareController.sumData[i] = s;
                        MagicSquareController.sumData[i+1] = s;
                        MagicSquareController.sumLables[i].setText(""+MagicSquareController.sumData[i]);
                        MagicSquareController.sumLables[i+1].setText(""+MagicSquareController.sumData[i+1]);
                    }

                    // right upper 2*dimension+2
                    s0 = 0;
                    for(int i=1;i<=MagicSquareController.dimension;i++){
                        s0 += data[i][MagicSquareController.dimension+1-i];
                    }
                    MagicSquareController.sumData[2*MagicSquareController.dimension+2] = s0;
                    MagicSquareController.sumData[2*MagicSquareController.dimension+3] = s0;
                    MagicSquareController.sumLables[2*MagicSquareController.dimension+2].setText(""+MagicSquareController.sumData[2*MagicSquareController.dimension+2]);
                    MagicSquareController.sumLables[2*MagicSquareController.dimension+3].setText(""+MagicSquareController.sumData[2*MagicSquareController.dimension+3]);

                    //right 2*dimension+2+i
                    for(int i=2; i<=2*MagicSquareController.dimension; i+=2) {
                        int s = 0;
                        for (int k = 1; k <= MagicSquareController.dimension; k++) {
                            s += data[i / 2][k];
                        }
                        MagicSquareController.sumData[2 * MagicSquareController.dimension + 2 + i] = s;
                        MagicSquareController.sumData[2 * MagicSquareController.dimension + 3 + i] = s;
                        MagicSquareController.sumLables[2 * MagicSquareController.dimension + 2 + i].setText("" + MagicSquareController.sumData[2 * MagicSquareController.dimension + 2 + i]);
                        MagicSquareController.sumLables[2 * MagicSquareController.dimension + 3 + i].setText("" + MagicSquareController.sumData[2 * MagicSquareController.dimension + 3 + i]);

                    }
                    break;
                }


            }

            if (tempFitness != fitness) {
                temperature /= COOLING_FRACTION;
            }

            if (flag) {
                stuckcount += 1;
            } else {
                stuckcount = 0;
            }

            if (stuckcount > 5000) {
                stuckcount = 0;
                temperature += 2;

            }

            //System.out.println("Iteration #" + iteration + ": best fitness: " + fitness);
           // System.out.println("tem" + temperature);


            iteration++;
            MagicSquareController.generation = iteration;
            MagicSquareController.error = fitness;
            if(MagicSquareController.generation % 10000 == 1){

                for(int i=0;i<MagicSquareController.dimension;i++){
                    for(int k=0;k<MagicSquareController.dimension;k++){
                        MagicSquareController.data[i+1][k+1] = square[i][k];
                        MagicSquareController.labels[i+1][k+1].setText(""+MagicSquareController.data[i+1][k+1]);
                    }
                }
                MagicSquareController.generationLabel.setText("  Generation: " +MagicSquareController.generation);
                MagicSquareController.errorLabel.setText("  Error: " +MagicSquareController.error);

                //left upper 0
                int s0 = 0;
                for(int i=1;i<=MagicSquareController.dimension;i++){
                    s0 += data[i][i];
                }
                MagicSquareController.sumData[0] = s0;
                MagicSquareController.sumData[1] = s0;
                MagicSquareController.sumLables[0].setText(""+MagicSquareController.sumData[0]);
                System.out.println("更新"+MagicSquareController.generation);
                MagicSquareController.sumLables[1].setText(""+MagicSquareController.sumData[1]);

                //upper 2,4,...,2*dimension
                for(int i=2;i<=2*MagicSquareController.dimension;i+=2){
                    int s = 0;
                    for(int k=1;k<=MagicSquareController.dimension;k++){
                        s += data[i/2][k];
                    }
                    MagicSquareController.sumData[i] = s;
                    MagicSquareController.sumData[i+1] = s;
                    MagicSquareController.sumLables[i].setText(""+MagicSquareController.sumData[i]);
                    MagicSquareController.sumLables[i+1].setText(""+MagicSquareController.sumData[i+1]);
                }

                // right upper 2*dimension+2
                s0 = 0;
                for(int i=1;i<=MagicSquareController.dimension;i++){
                    s0 += data[i][MagicSquareController.dimension+1-i];
                }
                MagicSquareController.sumData[2*MagicSquareController.dimension+2] = s0;
                MagicSquareController.sumData[2*MagicSquareController.dimension+3] = s0;
                MagicSquareController.sumLables[2*MagicSquareController.dimension+2].setText(""+MagicSquareController.sumData[2*MagicSquareController.dimension+2]);
                MagicSquareController.sumLables[2*MagicSquareController.dimension+3].setText(""+MagicSquareController.sumData[2*MagicSquareController.dimension+3]);

                //right 2*dimension+2+i
                for(int i=2; i<=2*MagicSquareController.dimension; i+=2) {
                    int s = 0;
                    for (int k = 1; k <= MagicSquareController.dimension; k++) {
                        s += data[i / 2][k];
                    }
                    MagicSquareController.sumData[2 * MagicSquareController.dimension + 2 + i] = s;
                    MagicSquareController.sumData[2 * MagicSquareController.dimension + 3 + i] = s;
                    MagicSquareController.sumLables[2 * MagicSquareController.dimension + 2 + i].setText("" + MagicSquareController.sumData[2 * MagicSquareController.dimension + 2 + i]);
                    MagicSquareController.sumLables[2 * MagicSquareController.dimension + 3 + i].setText("" + MagicSquareController.sumData[2 * MagicSquareController.dimension + 3 + i]);

                }
            }
        } while (System.nanoTime() - startTime < allowedTimeNanoSec);


        return new MagicSquareSolution(square, fitness, iteration);
    }



    public static int sum(Random rand, int[][] square, int n, int swapCount, int m, int oldFitness) {
        int result = oldFitness;
        int squareLength = square.length;
        int[][] copy = new int[square.length][square.length];
        for (int i = 0; i < squareLength; i++) {
            for (int j = 0; j < squareLength; j++) {
                copy[i][j] = square[i][j];
            }
        }

        int r1 = 0, r2 = 0, c1 = 0, c2 = 0;
        int swapOne = -1;
        int swapTwo = -1;
        for (int i = 0; i < swapCount; i++) {
            r1 = rand.nextInt(n);
            c1 = rand.nextInt(n);
            r2 = rand.nextInt(n);
            c2 = rand.nextInt(n);
            if (fixSquare[r1][c1] != 1 && fixSquare[r2][c2] != 1 && !(r1 == r2 && c1 == c2)) {
                swapOne = copy[r1][c1];
                swapTwo = copy[r2][c2];
                int temp = copy[r1][c1];
                copy[r1][c1] = copy[r2][c2];
                copy[r2][c2] = temp;
            } else {
                i--;
            }

        }

        mutatedSquare = copy;

        int fitness = oldFitness;
        int difference = swapOne - swapTwo;


        int distanceOne = Math.abs(sumRow[r1] - m);
        int distanceTwo = Math.abs(sumRow[r1] - difference - m);
        sumRow[r1]-=difference;
        fitness -= (distanceOne - distanceTwo);

        distanceOne = Math.abs(sumRow[r2] - m);
        distanceTwo = Math.abs(sumRow[r2] + difference - m);
        sumRow[r2]+=difference;
        fitness -= (distanceOne - distanceTwo);

        distanceOne = Math.abs(sumColumn[c1] - m);
        distanceTwo = Math.abs(sumColumn[c1] - difference - m);
        sumColumn[c1]-=difference;

        fitness -= (distanceOne - distanceTwo);

        distanceOne = Math.abs(sumColumn[c2] - m);
        distanceTwo = Math.abs(sumColumn[c2] + difference - m);
        sumColumn[c2]+=difference;
        fitness -= (distanceOne - distanceTwo);

        if (r1 == c1) {
            distanceOne = Math.abs(leftDiagonal - m);
            distanceTwo = Math.abs(leftDiagonal - difference - m);
            leftDiagonal-=difference;
            fitness -= (distanceOne - distanceTwo);
        }
        if (r2 == c2) {
            distanceOne = Math.abs(leftDiagonal - m);
            distanceTwo = Math.abs(leftDiagonal + difference - m);
            leftDiagonal+=difference;
            fitness -= (distanceOne - distanceTwo);
        }

        if (r1 + c1 == n-1) {
            distanceOne = Math.abs(rightDiagonal - m);
            distanceTwo = Math.abs(rightDiagonal - difference - m);
            rightDiagonal-=difference;
            fitness -= (distanceOne - distanceTwo);
        }
        if (r2 + c2 == n-1) {
            distanceOne = Math.abs(rightDiagonal - m);
            distanceTwo = Math.abs(rightDiagonal + difference - m);
            rightDiagonal+=difference;
            fitness -= (distanceOne - distanceTwo);
        }

        result = fitness;


if(fitness>=oldFitness){
    sumRow[r1]+=difference;
    sumRow[r2]-=difference;
    sumColumn[c1]+=difference;
    sumColumn[c2]-=difference;
if(r1 == c1){
    leftDiagonal+=difference;
}
if(r2==c2){
    leftDiagonal-=difference;
}
if (r1 + c1 == n-1) {
    rightDiagonal+=difference;
}
if(r2+c2==n-1){
    rightDiagonal-=difference;
}

}

        return result;
    }

    public static int[][] makeInitialSquare(Random rand, int n, boolean constrained,
                                            int cRow, int cCol, int[][] square) {
        fixSquare = new int[n][n];
        int nSquared = n * n;
        int numPoolStart = 1;
        int numPoolSize = nSquared;

        int numPoolIndex = 0;
        ArrayList<Integer> numPool = new ArrayList<Integer>(numPoolSize);

        for (int i = numPoolStart; i <= nSquared; i++) {
            numPool.add(numPoolIndex++, i);
        }

        ArrayList<Integer> fixPool = new ArrayList<Integer>(numPoolSize);
        if (constrained) {


            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (square[i][j] != 0) {
                        fixPool.add(square[i][j]);
                        fixSquare[i][j] = 1;
                    }
                }
            }
            Collections.sort(fixPool);

            while (!fixPool.isEmpty()) {
                int index = Collections.binarySearch(numPool, fixPool.get(0));
                fixPool.remove(0);
                numPool.remove(index);
            }

        }


        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (0 == square[i][j]) {
                    int indx = rand.nextInt(numPool.size());
                    square[i][j] = numPool.get(indx);

                    numPool.remove(indx);
                }
            }
        }

        return square;
    }

    public static int evalFitness(int[][] square, int n, int m)
    {
        int fitness            = 0;

        int totalRowDiff       = 0;
        int totalColDiff       = 0;
        int leftToRightDiagSum = 0;
        int rightToLeftDiagSum = 0;

        for(int i=0; i<n; i++)
        {
            int rowSum = 0;
            int colSum = 0;

            for(int j=0; j<n; j++)
            {
                rowSum += square[i][j];
                colSum += square[j][i];

                if(i == j)
                {
                    leftToRightDiagSum += square[i][j];
                }

                if(i+j == n-1)
                {
                    rightToLeftDiagSum += square[i][j];
                }
            }
sumRow[i]=rowSum;
        sumColumn[i]=colSum;
            totalRowDiff += Math.abs(rowSum-m);
            totalColDiff += Math.abs(colSum-m);
        }
leftDiagonal=leftToRightDiagSum;
        rightDiagonal=rightToLeftDiagSum;

        fitness = Math.abs(leftToRightDiagSum-m) + Math.abs(rightToLeftDiagSum-m) + totalRowDiff + totalColDiff;

        return fitness;
    }


}
